﻿/*!
 * This Source Code is subject to the terms of the Mozilla Public License
 * version 2.0 (the "License"). You can obtain a copy of the License at
 * http://mozilla.org/MPL/2.0/.
 */

(function(){
	var Filter = {
		regexpRegExp : /^(@@)?\/.*\/(?:\$~?[\w\-]+(?:=[^,\s]+)?(?:,~?[\w\-]+(?:=[^,\s]+)?)*)?$/,
		elemhideRegExp : /^([^\/\*\|\@"!]*?)#(?:([\w\-]+|\*)((?:\([\w\-]+(?:[$^*]?=[^\(\)"]*)?\))*)|#([^{}]+))$/,
		optionsRegExp : /\$(~?[\w\-]+(?:=[^,\s]+)?(?:,~?[\w\-]+(?:=[^,\s]+)?)*)$/,
		fromText : function(text){
			if(this.elemhideRegExp.test(text))
				return ElemHideFilter.fromText(text, RegExp["$1"], RegExp["$2"], RegExp["$3"], RegExp["$4"]);
			else
				return RegExpFilter.fromText(text);
		},
		normalize : function(text){
			text = text.replace(/[^\S ]/g, "");
			if(/^\s*!/.test(text))
				return text.replace(/^\s+/, "").replace(/\s+$/, "");
			else if(this.elemhideRegExp.test(text)){
				/^(.*?)(#+)(.*)$/.test(text);
				var domain = RegExp["$1"], separator = RegExp["$2"], selector = RegExp["$3"];
				return domain.replace(/\s/g, "") + separator + selector.replace(/^\s+/, "").replace(/\s+$/, "");
			}else
				return text.replace(/\s/g, "");
		}
	}

	function ActiveFilter(text, domainSource){
		this.text = text;
		if(domainSource){
			var domains = domainSource.split(this.domainSeparator);
			if(domains.length === 1 && domains[0][0] !== "~") {
			  this.domains = {__proto__:null, "":false};
			  this.domains[domains[0]] = true;
			} else {
			  var hasIncludes = false;
			  for(var i = 0, l = domains.length; i < l; ++i){
				var domain = domains[i];
				if(domain === ""){
				  continue;
				}
				var include;
				if(domain[0] === "~"){
					include = false;
					domain = domain.substr(1)
				} else {
					include = true;
					hasIncludes = true;
				}
				if(!this.domains)
					this.domains = {__proto__:null};
				this.domains[domain] = include;
			  }
			  this.domains[""] = !hasIncludes;
			}
		}
	}
	ActiveFilter.isActiveOnDomain = function(docDomain){
		"use strict";
		if(!this.domains)
			return true;
		docDomain = docDomain.toUpperCase();
		do{
			if(typeof this.domains[docDomain] !== 'undefined')
				return this.domains[docDomain];
			var nextDot = docDomain.indexOf(".");
			if(nextDot < 0)
				break;
			docDomain = docDomain.substr(nextDot + 1);
		}while(true);
		return this.domains[""];
	}

  function RegExpFilter(text, regexpSource, contentType, matchCase, domains, thirdParty){
    ActiveFilter.call(this, text, domains);
    if(contentType)
        this.contentType = contentType;
    if(matchCase)
        this.matchCase = matchCase;
    if(thirdParty)
        this.thirdParty = thirdParty;
		
	if (regexpSource.length >= 2 && regexpSource[0] === "/" && regexpSource[regexpSource.length - 1] === "/"){
		// The filter is a regular expression - convert it immediately.
		this.regexp = new RegExp(regexpSource.substr(1, regexpSource.length - 2), this.matchCase ? "" : "i");
	} else {
		var source = regexpSource.replace(/\*+/g, "*");
		if(source[0] === "*")
			source = source.substr(1);
		var pos = source.length - 1;
		if(pos >= 0 && source[pos] === "*")
			source = source.substr(0, pos);
		source = source.replace(/\^\|$/, "^").replace(/\W/g, "\\$&").replace(/\\\*/g, ".*").replace(/\\\^/g, "(?:[\\x00-\\x24\\x26-\\x2C\\x2F\\x3A-\\x40\\x5B-\\x5E\\x60\\x7B-\\x80]|$)").replace(/^\\\|\\\|/, "^[\\w\\-]+:\\/+(?!\\/)(?:[^.\\/]+\\.)*?").replace(/^\\\|/, "^").replace(/\\\|$/, "$");
		this.regexp = new RegExp(source, this.matchCase ? "" : "i");
	}
  }
  RegExpFilter.prototype = {
	isActiveOnDomain: ActiveFilter.isActiveOnDomain,
	domainSeparator: "|",
	contentType: (1 << 20) - 1,
	matchCase: false,
	matches:function(location, contentType, docDomain, thirdParty){
		"use strict";
		return((RegExpFilter.typeMap[contentType] & this.contentType) && this.isActiveOnDomain(docDomain) && this.regexp.test(location) && (!this.thirdParty || this.thirdParty === thirdParty))
	},
	__proto__: null
  };
  RegExpFilter.typeMap = {
	OTHER: 1<<0,
	SCRIPT: 1<<1,
	IMAGE: 1<<2,
	STYLESHEET: 1<<3,
	OBJECT: 1<<4,
	OBJECT_SUBREQUEST: 1<<4,
	SUBDOCUMENT: 1<<5,
	DOCUMENT: 1<<6,
	XMLHTTPREQUEST: 1<<7,
	POPUP: 1<<11,
	DONOTTRACK: 1<<12,
	ELEMHIDE: 1<<13,
	__proto__:null
  };
  RegExpFilter.prototype.contentType &= ~(RegExpFilter.typeMap.ELEMHIDE | RegExpFilter.typeMap.DONOTTRACK | RegExpFilter.typeMap.POPUP);
  RegExpFilter.fromText = function(text){
    var origText = text,
		blocking = true;
    if(text.indexOf("@@") === 0){
      blocking = false;
      text = text.substr(2);
    }
    var contentType = matchCase = domains = thirdParty = '',
		options;
    if(Filter.optionsRegExp.test(text)){
      options = RegExp["$1"].toUpperCase().split(",");
      text = RegExp.leftContext;
      for(var i = 0; i < options.length; ++i){
        var option = options[i],
			value = null,
			separatorIndex = option.indexOf("=");
        if(separatorIndex >= 0){
          value = option.substr(separatorIndex + 1);
          option = option.substr(0, separatorIndex);
        }
        option = option.replace('-', "_");
        if(option in RegExpFilter.typeMap){
          contentType = contentType || 0;
          contentType |= RegExpFilter.typeMap[option];
        }else if(option[0] === "~" && option.substr(1) in RegExpFilter.typeMap){
            contentType = contentType || RegExpFilter.prototype.contentType;
            contentType &= ~RegExpFilter.typeMap[option.substr(1)];
        }else if(option === "MATCH_CASE")
            matchCase = true;
        else if(option === "DOMAIN" && value)
            domains = value;
        else if(option === "THIRD_PARTY")
            thirdParty = true;
        else if(option === "~THIRD_PARTY")
            thirdParty = false;
      }
    }
    if(!blocking && (!contentType || contentType & RegExpFilter.typeMap.DOCUMENT) && (!options || options.indexOf("DOCUMENT") < 0) && !/^\|?[\w\-]+:/.test(text)) {
      contentType = contentType || RegExpFilter.prototype.contentType;
      contentType &= ~RegExpFilter.typeMap.DOCUMENT;
    }
    if(blocking)
        return new BlockingFilter(origText, text, contentType, matchCase, domains, thirdParty)
    else
        return new WhitelistFilter(origText, text, contentType, matchCase, domains, thirdParty)
  };

  function BlockingFilter(text, regexpSource, contentType, matchCase, domains, thirdParty){
    RegExpFilter.call(this, text, regexpSource, contentType, matchCase, domains, thirdParty);
  }
  function WhitelistFilter(text, regexpSource, contentType, matchCase, domains, thirdParty){
    RegExpFilter.call(this, text, regexpSource, contentType, matchCase, domains, thirdParty);
  }
  BlockingFilter.prototype =  { __proto__: RegExpFilter.prototype };
  WhitelistFilter.prototype = { __proto__: RegExpFilter.prototype };

  function ElemHideFilter(text, domains, selector){
    ActiveFilter.call(this, text, domains ? domains.toUpperCase() : null);
    this.selector = selector;
  }
  ElemHideFilter.prototype = {isActiveOnDomain: ActiveFilter.isActiveOnDomain, domainSeparator: ",", __proto__: null};
  ElemHideFilter.fromText = function(text, domain, tagName, attrRules, selector){
    if(!selector){
      if(tagName === "*")
        tagName = "";
      var id = additional = "";
      if(attrRules){
        attrRules = attrRules.match(/\([\w\-]+(?:[$^*]?=[^\(\)"]*)?\)/g);
        for(var i = 0; i < attrRules.length; ++i){
          var rule = attrRules[i];
          rule = rule.substr(1, rule.length - 2);
          var separatorPos = rule.indexOf("=");
          if(separatorPos > 0){
            rule = rule.replace(/=/, '="') + '"';
            additional += "[" + rule + "]"
          }else {
            id = rule;
          }
        }
      }
      if(id)
        selector = tagName + "." + id + additional + "," + tagName + "#" + id + additional;
      else if(tagName || additional)
        selector = tagName + additional;
      else
        return;
    }
    return new ElemHideFilter(text, domain, selector);
  };

  window.Filter = Filter;
  window.RegExpFilter = RegExpFilter;
  window.BlockingFilter = BlockingFilter;
  window.WhitelistFilter = WhitelistFilter;
  window.ElemHideFilter = ElemHideFilter;
})();